#include "VitterHuffmanCodec.h"

#include <bitset>
#include <iostream>
#include <climits>

using namespace Huffman;

VitterHuffmanCodec::VitterHuffmanCodec(std::string inputFile, std::string outputFile) 
	: HuffmanCodec(inputFile, outputFile)
{	
	// intializes the symbol tree
	symbolTree = new Tree();
	symbolTree -> setMaxAllowedWeight(ULONG_MAX / 2);
}

VitterHuffmanCodec::~VitterHuffmanCodec(void)
{
	// empty destructor
}

void VitterHuffmanCodec::encode() 
{
	printEncoding();

	timeStart = clock();

	while(!inputDataStream -> isEof()) 
	{		
		encodeSymbol();
	}

	timeEnd = clock();

	closeStreams();

	printSummary(true);
}

void VitterHuffmanCodec::decode()
{
	printDecoding();

	timeStart = clock();

	bool eocFound = false;
	while(!eocFound) 
	{		
		eocFound = decodeSymbol();
	}

	timeEnd = clock();

	closeStreams();

	printSummary(false);
}

void VitterHuffmanCodec::closeStreams() 
{

	inputDataStream -> close();	
	outputDataStream -> flush();
	outputDataStream -> close();
}

void VitterHuffmanCodec::encodeSymbol() 
{	
	Node* node;
	int encodedSymbol = 0;
	char symbolBytes = 0;
	char symbol = 0;

	// read entire byte from input
	symbol = inputDataStream -> read();

	// finds the coresponding node in the symbol tree or the NYT node
	node = symbolTree -> findNode(symbol);		

	// checks if the last symbol has been already 
	if ( inputDataStream -> isEof() ){ 
		writeEncodedSymbol(getEncodedSymbolCodeFromNode(Tree::EOC));		
	} else {

		if(node == 0)
		{						
			writeEncodedSymbol(getEncodedSymbolCodeFromNode(Tree::NYT));
			writeSymbol(symbol);
		} else {		
			EncodedSymbol* s = getEncodedSymbolCodeFromNode(node);
			writeEncodedSymbol(s);
			delete s;
		}

		symbolTree -> updateTreeVitter(symbol);		

		symbolTree -> printOnConsole();
	}
}

int VitterHuffmanCodec::getSymbolCodeFromNode(Node* node) {

	int result = 0;	

	if ( node -> getParent() == 0 && node == symbolTree -> getRoot() ){
		return node -> getSymbol();
	}

	while(node -> getParent() != 0) {		
		result <<= 1;
		result |= node -> getParity();

		node = node -> getParent();		
	}

	return result;
}

EncodedSymbol* VitterHuffmanCodec::getEncodedSymbolCodeFromNode(Node* node) {
	/*std::cout << "BEGIN: Get encoded symbol code from node " << node -> getSymbol() << std::endl;*/
	int result = 0;	
	int bitCount = 0;

	if ( node -> getParent() == 0 ){ //&& node == symbolTree -> getRoot() 
		return new EncodedSymbol(node -> getSymbol(), 1); //NYT
	}

	while(node -> getParent() != 0) {		
		result <<= 1;
		result |= node -> getParity();

		node = node -> getParent();		
		bitCount++;
	}

	/*std::cout << "END: Get encoded symbol code from node " << node -> getSymbol() << std::endl;*/
	return new EncodedSymbol(result, bitCount);
}

char VitterHuffmanCodec::getSymbolBytes(int symbol) {

	// TODO not sure what exactly this should do, but in my understanding 
	// this method should just return the symbol from input, eventually 
	// in a form of byte array
	// TODO consider using std::bitset instead of int values
	return symbol;
}

void VitterHuffmanCodec::writeSymbolBytes(int bytes) {

	while(bytes > 0) {
		outputDataStream -> write(bytes & 1);

		bytes >>= 1;
	}
}

void VitterHuffmanCodec::writeEncodedSymbol(EncodedSymbol* symbol) {

	outputDataStream -> writeEncodedSymbol(symbol);

}

void VitterHuffmanCodec::writeSymbol(char symbol) {

	outputDataStream -> write(symbol);
}

bool VitterHuffmanCodec::decodeSymbol() 
{	
	bool val;
	Node* node = symbolTree -> getRoot();

	int decodedSymbol;

	// iterates over the bits of the value
	do
	{
		// reads a single bit from the input
		val = inputDataStream -> readBit();				

		if(val) 
		{
			node = node -> getRight();
		} else 
		{
			node = node -> getLeft();
		}				

	} while(node -> getNodeType() != LEAF);

	if(node == symbolTree -> EOC) {
		return true;
	}

	if(node == symbolTree -> NYT) 
	{
		if(!outputDataStream -> isEof()) {
			decodedSymbol = readSymbolBytes();			
		}
	} else {
		decodedSymbol = node -> getSymbol();
	}

	writeSymbol(decodedSymbol);
	symbolTree -> updateTreeVitter(decodedSymbol);
	return false;
}

char VitterHuffmanCodec::readSymbolBytes() 
{
	return inputDataStream -> read();
}

void VitterHuffmanCodec::printEncoding()
{
	std::cout << "Kompresja pliku: " << inputDataStream -> getPath() << std::endl;
	std::cout << "Zapisywanie wyniku do: " << outputDataStream -> getPath() << std::endl;
}

void VitterHuffmanCodec::printDecoding()
{
	std::cout << "Dekompresja pliku: " << inputDataStream -> getPath() << std::endl;
	std::cout << "Zapisywanie wyniku do: " << outputDataStream -> getPath() << std::endl;
}

void VitterHuffmanCodec::printSummary(bool encoding) 
{
	std::cout << "----------------------------------------------------------" << std::endl;
	std::cout << "Zakonczono" << std::endl;
	std::cout << "Podsumowanie:" << std::endl;
	std::cout << "- Czas trwania: " << (double)(1000 * (timeEnd - timeStart)) / (CLOCKS_PER_SEC) << " ms"<< std::endl;
	std::cout << "- Rozmiar pliku wejsciowego: " << inputDataStream -> readByteCounter << " bajtow" << std::endl;
	std::cout << "- Rozmiar pliku wynikowego: " << outputDataStream -> writeByteCounter << " bajtow" << std::endl;

	if (encoding) 
	{
		int inputSize = inputDataStream -> readByteCounter;
		int outputSize = outputDataStream -> writeByteCounter;

		double compressRatio = 1.0 - (outputSize / (double)inputSize);
		double bitsPerByte = 8 * (1.0 - compressRatio);

		std::cout << "- Stopien kompresji: " << compressRatio << std::endl;
		std::cout << "- Liczba bitow na bajt: " << bitsPerByte << std::endl;
	}
}